package parsing.productions;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import parsing.InvalidSyntaxException;
import world.Critter;


public class Atom extends MutableNode implements NumberNode {
	// Invariant for children: if type is NUM, children is empty.
	// If type is MEM, TERM, or RANDOM children has one Term.
	// If type is SENSOR, children has one Sensor.

	public static final int NUM 	= 1;
	public static final int MEM 	= 2;
	public static final int TERM 	= 3;
	public static final int SENSOR	= 4;
	public static final int RANDOM	= 5;
	
	private int number; // If type is NUM, number stores the value of that number.
	private int type; // Type of this Atom. Can only have one type.
	
	/**
	 * Constructor: creates an instance of Atom with type MEM, TERM, or RANDOM.
	 * @param at AtomType of this Atom.
	 * @param t this Atom's term.
	 * Checks: t is MEM, TERM, or RANDOM
	 */
	public Atom(int at, Term t) {
		if (!(at == TERM || at == MEM || at == RANDOM)) {
			throw new InvalidSyntaxException("Only MEM, TERM, or RANDOM Atoms have a Term parameter.");
		}
		type= at;
		if(t != null)
			children.add(t);
		else
			throw new IllegalArgumentException("The Term child of an Atom cannot be null.");
	}
	
	/**
	 * Constructor: creates an instance of Atom with type SENSOR.
	 * @param s This atom's Sensor Node.
	 */
	public Atom(Sensor s) {
		type= SENSOR;
		if(s != null)
			children.add(s);
		else
			throw new IllegalArgumentException("The Sensor child of an Atom cannot be null.");
	}
	
	/**
	 * Constructor: creates an instance of Atom with type NUM.
	 * @param num the value of this Atom.
	 */
	public Atom(int num) {
		type= NUM;
		number= num;
	}

	@Override
	public void prettyPrint(StringBuffer sb) {
		switch(type){
		case NUM: sb.append(number).append(" "); break;
		case MEM:
			sb.append("mem[");
			children.get(0).prettyPrint(sb);
			sb.deleteCharAt(sb.length() - 1);//removes the space before the ]
			sb.append("] ");
			break;
		case TERM:
			sb.append("(");
			children.get(0).prettyPrint(sb);
			sb.deleteCharAt(sb.length() - 1);//removes the space before the ]
			sb.append(") ");
			break;
		case SENSOR: children.get(0).prettyPrint(sb); break;
		case RANDOM:
			sb.append("random[");
			children.get(0).prettyPrint(sb);
			sb.deleteCharAt(sb.length() - 1);//removes the space before the ]
			sb.append("] ");
			break;
		}
	}

	@Override
	public void mutate(Program genome) {
		Random rnd = new Random();
//		StringBuffer sb = new StringBuffer();
//		prettyPrint(sb);
//		sb.append("was changed to ");
		if (type == NUM) { // Mutates according to possible mutations for NUM.
			int mutationType = rnd.nextInt(2);
			if (mutationType == 0) {//changes to mem[number]
				type = MEM;
				List<AbstractNode> atomList = new LinkedList<AbstractNode>();
				List<AbstractNode> factorList = new LinkedList<AbstractNode>();
				atomList.add(new Atom(number));
				factorList.add(new Factor(atomList));
				children.add(new Term(factorList));
			} else {
				int newNum;
				do{
					newNum = rnd.nextInt(7);
				} while(newNum == number);
				number = newNum;
			}
		} else if (type == SENSOR) { // Mutates according to possible mutations for SENSOR.
			int newType;
			do{
				newType = rnd.nextInt(4) + 1;
			} while (newType == type);
			type = newType;
			children.clear();
			if(type == NUM){
				number = rnd.nextInt(7);
			}
			else{ //new type is MEM or TERM
				List<AbstractNode> atomList = new LinkedList<AbstractNode>();
				List<AbstractNode> factorList = new LinkedList<AbstractNode>();
				atomList.add(new Atom(rnd.nextInt(7)));
				factorList.add(new Factor(atomList));
				children.add(new Term(factorList));
			}
		} else { // Type is MEM or TERM. Mutates according to possible mutations for MEM or TERM.
			int newType;
			do{
				newType = rnd.nextInt(4) + 1;
			} while(type == newType || newType == SENSOR);//impossible to change to a Sensor
			type = newType;
			if(type == NUM){
				children.clear();
				number = rnd.nextInt(7);
			}					
		}
//		prettyPrint(sb);
//		System.out.println(sb);
	}

	@Override
	public Node duplicate() {
		if (type == NUM) {
			return new Atom(number);
		} else if (type == SENSOR) {
			Sensor child= (Sensor)children.get(0).duplicate();
			return new Atom(child);
		} else { // type is MEM or TERM
			Term child= (Term)children.get(0).duplicate();
			return new Atom(type, child);
		}
	}

	/**
	 * Checks if this Atom is the same as n
	 * @param n the node to check against
	 * @return true if n is an Atom and its type is the same as this one's
	 */
	@Override
	public boolean equals(Node n){
		if(!(n instanceof Atom))
			return false;
		Atom rNode = (Atom) n;
		if(type != rNode.type)
			return false;
		if(type == NUM)
			return number == rNode.number;
		return super.equals(rNode);//if this Atom has children and everything else lines up, check the children
	}

	@Override
	public double getValue(Critter c) {
		if(type == NUM)
			return number;
		else if (type == MEM){
			int index = (int) ((Term) children.get(0)).getValue(c);
			if (index < 0)
				index = -index;
			return c.getMemValue(index % c.getMemValue(0));
		}
		else if (type == RANDOM){
			int range = (int) ((Term) children.get(0)).getValue(c);
			if (range < 0)
				range = -range;
			return (int) (Math.random() * range);
		}
		else //TERM or SENSOR
			return ((NumberNode) children.get(0)).getValue(c);
	}
}
